package com.hoku.account.config;

import feign.*;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration;

import java.util.Calendar;
import java.util.concurrent.TimeUnit;

/**
 * OAuth2配置
 * @author ZHUFEIFEI
 */
@Configuration
public class OAuth2ClientConfig {

    /**
     * 定义oauth2客户端认证授权方式，主要用于feign请求时候的配置RequestInterceptor
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "security.oauth2.client")
    public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
        return new ClientCredentialsResourceDetails();
    }

    /**
     * oauth2请求拦截，在header中添加token信息
     * 在feign发送请求调用其他服务前从oauth2ClientContext中提取token封装到头部
     * @param oAuth2ClientContext {@link OAuth2ClientConfiguration}
     * @param details {@link #clientCredentialsResourceDetails()}
     * @return
     */
    @Bean
    public RequestInterceptor oauth2RequestInterceptor(OAuth2ClientContext oAuth2ClientContext
            , ClientCredentialsResourceDetails details) {
        return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, details);
    }

    /**
     * 定义feign日志打出信息量
     * ：https://cloud.spring.io/spring-cloud-openfeign/single/spring-cloud-openfeign.html
     *  {@link Feign} {@link org.springframework.cloud.openfeign.FeignClientProperties.FeignClientConfiguration}
     * @return
     */
    @Bean
    public Logger.Level feignLevel() {
        return Logger.Level.FULL;
    }

    /**
     * 覆盖FeignClientsConfiguration中的retry配置
     * @return
     */
    @Bean
    public Retryer retryer() {
        //100毫秒一次，最大间隔5秒，重试10次
        return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(5), 10);
    }

    /**
     * 错误解码器
     * @return
     */
    @Bean
    public ErrorDecoder errorDecoder() {
        return new RestClientErrorDecoder();
    }
}

/**
 * Feign调用HTTP返回响应码错误时候，定制错误的解码
 */
@Slf4j
class RestClientErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        log.error("feign invoke error, methodKey : {}, status : {} response : {}", methodKey
                , response.status(), response.body());
        if (HttpStatus.SC_UNAUTHORIZED == response.status()) {
            return new RetryableException("access_token invalid, retry ...",
                    Request.HttpMethod.valueOf(methodKey), Calendar.getInstance().getTime());
        }
        return FeignException.errorStatus(methodKey, response);
    }
}
/**
 * 定义支持oauth2的客户端类型请求的restTemplate,客户端信息来自于上面的clientCredentialsResourceDetails配置
 * ， 用于携带token发出请求，在需要使用的地方用法与restTemplate一样，用于发送请求的，暂时没用到也可以注释掉
 * @param details
 * @return
 *
 *  @Bean
 *  public OAuth2RestTemplate clientCredentialsRestTemplate(ClientCredentialsResourceDetails details) {
 *  return new OAuth2RestTemplate(details);
 * }
 */